/** @file tex_png.cpp
    @brief PNG images */

#include "tex/tex_png.h"

using namespace G3;

static void G3::png_stream_reader (png_structp png_ptr, png_bytep data, png_size_t length) {
	png_size_t check = 0;

	if (!data || !png_ptr || !png_ptr->io_ptr) {
        png_error (png_ptr, "Pointer Error");
        return;
	}

	if (length <= 0)
        return;

    G3::STREAM *stream = (STREAM *)(png_ptr->io_ptr);
	check = (png_size_t) stream->Read (data, length);

	if (check != length)
		png_error (png_ptr, "Stream Error");
}

static void G3::png_stream_writer (png_structp png_ptr, png_bytep data, png_size_t length) {
	int check = 0;

	if (!png_ptr || !png_ptr->io_ptr)
        png_error (png_ptr, "Pointer Error");

	check = ((STREAM *)(png_ptr->io_ptr))->Buffer (data, length);

	if (check != (int) length)
		png_error (png_ptr, "Stream Error");
}

static void G3::png_error_callback (png_structp png_ptr, png_const_charp error_msg) {
    printf ("ERROR! libpng: %s\n", error_msg);
    MainLog.Report ("ERROR! libpng: %s\n", error_msg);
    fprintf (stderr, "ERROR! libpng: %s\n", error_msg);
    longjmp (png_jmpbuf (png_ptr), 1);
}

static void G3::png_warning_callback (png_structp png_ptr, png_const_charp warning_msg) {
    printf ("WARNING! libpng: %s\n", warning_msg);
    MainLog.Report ("WARNING! libpng: %s\n", warning_msg);
    fprintf (stderr, "WARNING! libpng: %s\n", warning_msg);
}

boost::shared_ptr<G3::BITMAP> G3::LoadPNG (STREAM *Stream, LOG *Log) {
    boost::shared_ptr<G3::BITMAP> Bitmap;
	png_structp png_ptr = NULL;
	png_infop info_ptr = NULL;
    png_infop end_info_ptr = NULL;
	png_bytepp row_ptr = NULL;
	png_uint_32 width = 0;
	png_uint_32 height = 0;
	int depth = 0;
	int color_type = 0;
	long row_bytes = 0;

	Assert (Stream, "LoadPNG: Stream argument is a NULL pointer");

    try {
        //Bitmap = boost::shared_ptr<G3::BITMAP> (new BITMAP);
        Bitmap.reset (new BITMAP);

        if (!Bitmap)
            throw 1;
    } catch (...) {
        if (Log)
            Log->Report ("ERROR! LoadPNG: Not enough memory for a bitmap structure..\n");
        return boost::shared_ptr<G3::BITMAP>();
    }

    if (Log)
        Log->Report ("Using libpng version %s\n", PNG_LIBPNG_VER_STRING);

	// initialize the structures, info first for error handling
	png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, /*png_get_error_ptr (png_ptr)*/ NULL, png_error_callback, png_warning_callback);//NULL, NULL, NULL);

	if (!png_ptr) {
		if (Log)
			Log->Report ("ERROR! LoadPNG: Not enough memory for a PNG structure..\n");
		return boost::shared_ptr<G3::BITMAP>();
	}

	png_set_read_fn (png_ptr, (png_voidp) Stream, png_stream_reader);

	//png_set_error_fn (png_ptr, png_get_error_ptr (png_ptr), png_error_callback, png_warning_callback);

	info_ptr = png_create_info_struct (png_ptr);

	if (!info_ptr) {
		if (Log)
			Log->Report ("ERROR! LoadPNG: Not enough memory for a PNG info structure..\n");

        png_destroy_read_struct (&png_ptr, NULL, NULL);

		return boost::shared_ptr<G3::BITMAP>();
	}

	end_info_ptr = png_create_info_struct (png_ptr);

	if (!end_info_ptr) {
		if (Log)
			Log->Report ("ERROR! LoadPNG: Not enough memory for a PNG info structure..\n");

        png_destroy_read_struct (&png_ptr, &info_ptr, NULL);

		return boost::shared_ptr<G3::BITMAP>();
	}

	// We land here if a fatal error ocurrs
	if (setjmp (png_jmpbuf (png_ptr))) {
		if (Log)
			Log->Report ("LoadPNG: Fatal error, skipping..\n");

		png_destroy_read_struct (&png_ptr, &info_ptr, &end_info_ptr);

		Stream->Seek (Stream->Tell (STREAM::STREAM_END), STREAM::STREAM_SET);

		return boost::shared_ptr<G3::BITMAP>();
	}

	png_read_info (png_ptr, info_ptr);

	if (!png_get_IHDR (png_ptr, info_ptr, &width, &height, &depth,
					   &color_type, NULL, NULL, NULL)) {
		if (Log)
			Log->Report ("ERROR! LoadPNG: Failed to get PNG InfoHeader..\n");

		png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
		delete png_ptr;
		delete info_ptr;
		Stream->Seek (Stream->Tell (STREAM::STREAM_END), STREAM::STREAM_SET);
		return boost::shared_ptr<G3::BITMAP>();
	}

	Bitmap->Width = width;
	Bitmap->Height = height;

	// PNG is byte-aligned
	Bitmap->Flags |= BITMAP_ALIGNMENT;
	Bitmap->PackedAlignment = 1;

	if (color_type == PNG_COLOR_TYPE_GRAY ||
        color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
		Bitmap->Flags |= BITMAP_GRAYSCALE;
		depth = 8;
	}

	if (color_type == PNG_COLOR_TYPE_PALETTE || depth < 8) {
		png_set_expand (png_ptr);
		depth = 24;
	}

	if (color_type & PNG_COLOR_MASK_ALPHA) {
		Bitmap->Flags |= BITMAP_RGBA;
		depth = 32;
	}

	Bitmap->BitsPerPixel = depth;

	png_read_update_info (png_ptr, info_ptr);

	row_bytes = png_get_rowbytes (png_ptr, info_ptr);

	Bitmap->Size = row_bytes * Bitmap->Height;

	Bitmap->Data = new byte [Bitmap->Size];

	if (!Bitmap->Data) {
		if (Log)
			Log->Report ("ERROR! LoadPNG: Not enough memory for bitmap data..\n");

		png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
		delete png_ptr;
		delete info_ptr;
		Stream->Seek (Stream->Tell (STREAM::STREAM_END), STREAM::STREAM_SET);
		return boost::shared_ptr<G3::BITMAP>();
	}

	row_ptr = new png_bytep [Bitmap->Height];

	if (!row_ptr) {
		if (Log)
			Log->Report ("ERROR! LoadPNG: Not enough memory for row pointers..\n");

		delete [] Bitmap->Data;

		png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
		delete png_ptr;
		delete info_ptr;
		Stream->Seek (Stream->Tell (STREAM::STREAM_END), STREAM::STREAM_SET);
		return boost::shared_ptr<G3::BITMAP>();
	}

    for (int i=0; i<Bitmap->Height; i++)
        row_ptr [i] = Bitmap->Data + i * row_bytes;

	png_read_image (png_ptr, row_ptr);

	// Read until EOF
	png_read_end (png_ptr, NULL);

	delete [] row_ptr;

	png_destroy_read_struct (&png_ptr, &info_ptr, NULL);

	// Mark the start of the next file
	//Stream->Flush ();
	//Stream->SetFileStart ();

	return Bitmap;
}

bool G3::SavePNG (STREAM *stream, BITMAP *bitmap, LOG *log) {
	png_structp png_ptr = NULL;
	png_infop info_ptr = NULL;
	png_bytepp row_ptr = NULL;
	int color_type = 0;
	long row_bytes = 0;

	Assert (stream, "SavePNG: Stream argument is a NULL pointer");
	Assert (bitmap, "SavePNG: Bitmap argument is a NULL pointer");

	row_bytes = bitmap->Width * bitmap->BitsPerPixel / 8;

	// initialize the structures, info first for error handling
	png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, png_error_callback, png_warning_callback);

	if (!png_ptr) {
		if (log)
			log->Report ("ERROR! SavePNG: Not enough memory for a PNG structure..\n");
		return false;
	}

	info_ptr = png_create_info_struct (png_ptr);

	if (!info_ptr) {
		if (log)
			log->Report ("ERROR! SavePNG: Not enough memory for a PNG info structure..\n");
        png_destroy_write_struct (&png_ptr, NULL);
		return false;
	}

	// We land here if a fatal error ocurrs
	if (setjmp (png_jmpbuf (png_ptr))) {
		if (log)
			log->Report ("SavePNG: Fatal error, skipping..\n");

		png_destroy_write_struct (&png_ptr, &info_ptr);
		delete png_ptr;
		delete info_ptr;
		stream->Seek (stream->Tell (STREAM::STREAM_END), STREAM::STREAM_SET);
		return false;
	}

	png_set_write_fn (png_ptr, (png_voidp) stream, png_stream_writer, NULL);

	if (bitmap->BitsPerPixel <= 8) {
        if (bitmap->Flags & BITMAP_GRAYSCALE)
            color_type = PNG_COLOR_TYPE_GRAY;
        else
            color_type = PNG_COLOR_TYPE_PALETTE;
	} else if (bitmap->BitsPerPixel == 24 || bitmap->BitsPerPixel == 16)
        color_type = PNG_COLOR_TYPE_RGB;
    else if (bitmap->BitsPerPixel == 32)
        color_type = PNG_COLOR_TYPE_RGB_ALPHA;

	// set the zlib compression level
    png_set_compression_level (png_ptr, Z_BEST_COMPRESSION);

	png_set_IHDR (png_ptr, info_ptr, bitmap->Width, bitmap->Height, 16, color_type,
                PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

    png_set_packing (png_ptr);
	png_write_info (png_ptr, info_ptr);

    row_ptr = new png_bytep [bitmap->Height + 1];

    if (!row_ptr) {
		if (log)
			log->Report ("SavePNG: Fatal error, skipping..\n");

		png_destroy_write_struct (&png_ptr, &info_ptr);
		delete png_ptr;
		delete info_ptr;
		stream->Seek (stream->Tell (STREAM::STREAM_END), STREAM::STREAM_SET);
		return false;
    }

    for (int i=0; i<bitmap->Height; i++)
        row_ptr [i] = bitmap->Data + i * row_bytes;

	png_write_image (png_ptr, row_ptr);

	png_write_end (png_ptr, info_ptr);

	delete [] row_ptr;

	png_destroy_write_struct (&png_ptr, &info_ptr);

	return true;
}
